﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace IkSan.ExtJs4EF4
{
    /// <summary>
    /// Summary description for JsonHandler
    /// </summary>
    public class JsonHandler : IHttpHandler
    {
        private readonly JsonSerializer _jsonSerializer = new JsonSerializer
        {
            DefaultValueHandling = DefaultValueHandling.Ignore
        };

        public JsonHandler()
        {
            _jsonSerializer.Converters.Add(new IsoDateTimeConverter());
        }

        public void ProcessRequest(HttpContext context)
        {
            var path = context.Request.Url.AbsolutePath;
            var method = GetMethod(path);

            if (method == null)
            {
                SendResponse(context, new JsonResponse { message = string.Format("Unmapped request for {0}", path), success = false });
                return;
            }

            try
            {
                var parameters = GetParameters(context, method);
                var result = method.Invoke(this, parameters.ToArray());
                if(result is JsonResponse)
                {
                    SendResponse(context, (JsonResponse)result);
                    return;
                }
                SendResponse(context, new JsonResponse { data = result });
                return;
            }
            catch (TargetInvocationException targetInvocationException)
            {
                SendResponse(context, targetInvocationException.InnerException);
            }
            catch (Exception ex)
            {
                SendResponse(context, ex);
            }
        }

        private List<object> GetParameters(HttpContext context, MethodInfo method)
        {
            var parameters = new List<object>();

            // ReSharper disable LoopCanBeConvertedToQuery
            foreach (var parameter in method.GetParameters())
            // ReSharper restore LoopCanBeConvertedToQuery
            {
                var value = parameter.Name == "body" ? GetBody(context) : context.Request.QueryString[parameter.Name];
                parameters.Add(string.IsNullOrEmpty(value)
                                   ? GetDefaultValue(parameter.ParameterType)
                                   : _jsonSerializer.Deserialize(new StringReader(value), parameter.ParameterType));
            }
            return parameters;
        }

        private static string GetBody(HttpContext context)
        {
            string body = null;
            if (context.Request.RequestType == "POST")
            {
                using (var sr = new StreamReader(context.Request.InputStream))
                {
                    body = sr.ReadToEnd();
                }
            }
            return body;
        }

        private MethodInfo GetMethod(string path)
        {
            return GetType().GetMethods().Where(
                m =>
                m.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>().Where(
                    a => a.Description == path).Any()).FirstOrDefault();
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }

        static object GetDefaultValue(Type t)
        {
            return t.IsValueType ? Activator.CreateInstance(t) : null;
        }

        void SendResponse(HttpContext context, JsonResponse jsonResponse)
        {
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = jsonResponse.success ? 200 : 500;
            var stringWriter = new StringWriter();
            _jsonSerializer.Serialize(stringWriter, jsonResponse);
            context.Response.Write(stringWriter.GetStringBuilder().ToString());
        }

        void SendResponse(HttpContext context, Exception exception)
        {
            var e = exception;
            var messages = new List<string>();
            while (e != null)
            {
                messages.Add(e.Message);
                e = e.InnerException;
            }
            SendResponse(context, new JsonResponse { message = string.Join(" ", messages.ToArray()), stackTrace = exception.StackTrace, success = false });
        }
    }
}